package com.tcm.service.impl;

import cn.hutool.extra.pinyin.PinyinUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tcm.common.enums.CachePrefix;
import com.tcm.config.AppProperties;
import com.tcm.entity.*;
import com.tcm.entity.bo.SubjectItemExcel;
import com.tcm.entity.dto.param.SubjectItemPageParam;
import com.tcm.entity.dto.SubjectItemUpdateDto;
import com.tcm.entity.vo.BatchImportResult;
import com.tcm.entity.resp.PageR;
import com.tcm.entity.vo.SubjectItemListResult;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.exception.EntityInvalidException;
import com.tcm.mapper.*;
import com.tcm.service.*;
import com.tcm.utils.ExcelExportUtil;
import com.tcm.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 主观题业务实现类
 * @author Guqier
 * @version 1.0
 * @date 2023/7/20 16:11
 */
@Slf4j
@Service
public class SubjectItemServiceImpl extends ServiceImpl<SubjectItemMapper, SubjectItem> implements SubjectItemService {

    public static final int SUBJECT_TYPE = 0;// 主观题题目类型

    public static final int LABEL_PUBLIC_TYPE = 0;// 公共标签

    public static final String CODE_CATEGORY_TYPE = "category";

    private static final String CODE_DIFF_TYPE = "difficulty";// 码表难易程度类型

    private static final int NODE_TYPE = 1;// 书籍节点类型

    @Autowired
    private BookItemMapper bookItemMapper;

    @Autowired
    private LabelService labelService;

    @Autowired
    private ItemLabelMapper itemLabelMapper;

    @Autowired
    private ItemImageMapper itemImageMapper;

    @Autowired
    private BookService bookService;

    @Autowired
    private CodeService codeService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private TestPaperDetailMapper testPaperDetailMapper;

    @Lazy
    @Autowired
    private ItemStudentService itemStudentService;

    @Autowired
    private CollectService collectService;

    @Autowired
    private NoteService noteService;

    /**
     * 添加主观题
     * @param dto 主观题更新数据封装对象
     * @param currUserId 当前登录用户ID
     * @return 添加结果，true为成功
     */
    @Override
    @Transactional
    public boolean addSubjectItem(SubjectItemUpdateDto dto, Long currUserId){
        // 数据库操作结果，用于总体判断是否回滚
        boolean itemSave = false;// 题目保存
        int labelSave = 0;// 标签保存
        int imageSave = 0;// 配图保存
        boolean bookItemSave = false;// 题目章节保存结果
        // 构建实体对象
        SubjectItem subjectItem = new SubjectItem();
        BeanUtils.copyProperties(dto, subjectItem, "imageIds", "knowledgePoints", "bookId");
        subjectItem.setKnowledgePoint(String.join("|", dto.getKnowledgePoints()));
        subjectItem.setCreateUser(currUserId);
        subjectItem.setUpdateUser(currUserId);
        itemSave = this.save(subjectItem);

        // 校验章节信息
        Book book = bookService.getById(dto.getBookId());
        if (book == null){
            return false;
        }
        if (!book.getType().equals(NODE_TYPE)){
            throw new EntityInvalidException("题目只能所属于章节");
        }
        // 插入章节/题目关系
        BookItem bookItem = new BookItem();
        bookItem.setBookId(dto.getBookId());
        bookItem.setItemId(subjectItem.getId());
        bookItem.setType(SUBJECT_TYPE);
        bookItem.setCreateUser(currUserId);
        bookItem.setUpdateUser(currUserId);
        bookItemSave = bookItemMapper.insert(bookItem) == 1;

        // 添加标签
        LambdaQueryWrapper<Label> labelLambdaQueryWrapper = new LambdaQueryWrapper<>();
        labelLambdaQueryWrapper.eq(Label::getType, LABEL_PUBLIC_TYPE);// 只查询公共标签
        List<Label> labels = labelService.list(labelLambdaQueryWrapper);
        for (String point : dto.getKnowledgePoints()) {// 遍历标签判断是否已存在，已存在则引用
            Optional<Label> first = labels.stream().filter(label -> label.getName().equals(point)).findFirst();
            Long labelId = null;
            if (!first.isPresent()){// 如果标签不存在则创建一个
                Label label = new Label();
                label.setName(point);
                label.setType(LABEL_PUBLIC_TYPE);
                label.setCreateUser(currUserId);
                label.setUpdateUser(currUserId);
                if (!labelService.save(label)) {
                    break;// 保存失败直接跳过
                }
                labelId = label.getId();
            }else {// 已存在则直接引用已存在标签ID
                labelId = first.get().getId();
            }
            // 插入题目/标签关系表
            ItemLabel itemLabel = new ItemLabel();
            itemLabel.setItemId(subjectItem.getId());
            itemLabel.setLabelId(labelId);
            itemLabel.setType(LABEL_PUBLIC_TYPE);
            itemLabel.setCreateUser(currUserId);
            itemLabel.setUpdateUser(currUserId);
            labelSave += itemLabelMapper.insert(itemLabel);
        }

        // 添加图片关系
        List<Long> imageIds = dto.getImageIds();
        if (!CollectionUtils.isEmpty(imageIds)) {
            for (Long imageId : imageIds) {
                ItemImage itemImage = new ItemImage();
                itemImage.setItemId(subjectItem.getId());
                itemImage.setImageId(imageId);
                itemImage.setCreateUser(currUserId);
                itemImage.setUpdateUser(currUserId);
                imageSave += itemImageMapper.insert(itemImage);
            }
            if (imageSave != imageIds.size()) {
                throw new DatabaseOperationException("保存图片数据到数据库异常");
            }
        }
        // 判断结果正确性
        if (!itemSave || labelSave != dto.getKnowledgePoints().size() || !bookItemSave){
            throw new DatabaseOperationException("数据库操作异常");
        }
        return true;
    }

    /**
     * 更新主观题信息
     * @param dto 主观题更新数据封装对象
     * @param currUserId 当前登录用户ID
     * @return 处理结果，true为成功
     * @throws DatabaseOperationException 数据库操作异常
     */
    @Override
    @Transactional
    @CacheEvict(value = AppProperties.ENTITY_CACHE_ITEM_SUBJECT, key = "#dto.id", condition = "#dto.id != null")
    public boolean modifySubjectItem(SubjectItemUpdateDto dto, Long currUserId) throws DatabaseOperationException{
        // 更新实体类信息
        SubjectItem subjectItem = this.getById(dto.getId());
        if (subjectItem == null) {
            return false;
        }
        BeanUtils.copyProperties(dto, subjectItem, "bookId", "knowledgePoints", "imageIds");
        subjectItem.setKnowledgePoint(String.join("|", dto.getKnowledgePoints()));
        subjectItem.setUpdateUser(currUserId);
        if (!this.updateById(subjectItem)) {
            throw new DatabaseOperationException("实体类更新失败");
        }

        // 校验章节信息
        Book book = bookService.getById(dto.getBookId());
        if (book == null){
            return false;
        }
        if (!book.getType().equals(NODE_TYPE)){
            throw new EntityInvalidException("题目只能所属于章节");
        }
        // 更新题目/章节关联信息
        // 删除原有关系
        LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
        bookItemLambdaQueryWrapper.eq(BookItem::getItemId, subjectItem.getId());
        Integer count = bookItemMapper.selectCount(bookItemLambdaQueryWrapper);
        int deleteBookItem = bookItemMapper.delete(bookItemLambdaQueryWrapper);// 删除原有关系
        // 新增现有关系
        BookItem bookItem = new BookItem();
        bookItem.setBookId(dto.getBookId());
        bookItem.setItemId(subjectItem.getId());
        bookItem.setType(SUBJECT_TYPE);
        bookItem.setCreateUser(currUserId);
        bookItem.setUpdateUser(currUserId);
        boolean bookItemUpdate = bookItemMapper.insert(bookItem) == 1;

        if (count != deleteBookItem || !bookItemUpdate){
            throw new DatabaseOperationException("题目/章节关联关系更新失败");
        }

        // 更新图片信息
        // 删除原有图片
        int imageUpdate = 0;
        LambdaQueryWrapper<ItemImage> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        imageLambdaQueryWrapper.eq(ItemImage::getItemId, subjectItem.getId());
        Integer imageCount = itemImageMapper.selectCount(imageLambdaQueryWrapper);
        int imageDelete = itemImageMapper.delete(imageLambdaQueryWrapper);
        // 添加图片关系
        for (Long imageId : dto.getImageIds()) {
            ItemImage itemImage = new ItemImage();
            itemImage.setItemId(subjectItem.getId());
            itemImage.setImageId(imageId);
            itemImage.setCreateUser(currUserId);
            itemImage.setUpdateUser(currUserId);
            imageUpdate += itemImageMapper.insert(itemImage);
        }
        if (imageUpdate != dto.getImageIds().size() || imageDelete != imageCount){
            throw new DatabaseOperationException("图片/题目关系更新失败");
        }

        // 更新标签关系
        // 删除原有标签关系
        LambdaQueryWrapper<ItemLabel> labelLambdaQueryWrapper = new LambdaQueryWrapper<>();
        labelLambdaQueryWrapper.eq(ItemLabel::getItemId, subjectItem.getId());
        Integer labelCount = itemLabelMapper.selectCount(labelLambdaQueryWrapper);
        int labelDelete = itemLabelMapper.delete(labelLambdaQueryWrapper);
        // 添加标签题目关系
        int labelUpdate = 0;
        LambdaQueryWrapper<Label> labelsLambdaQueryWrapper = new LambdaQueryWrapper<>();
        labelsLambdaQueryWrapper.eq(Label::getType, LABEL_PUBLIC_TYPE);// 只查询公共标签
        List<Label> labels = labelService.list(labelsLambdaQueryWrapper);
        for (String point : dto.getKnowledgePoints()) {// 遍历标签判断是否已存在，已存在则应引用
            Optional<Label> first = labels.stream().filter(label -> label.getName().equals(point)).findFirst();
            Long labelId = null;
            if (!first.isPresent()){// 如果标签不存在则创建一个
                Label label = new Label();
                label.setName(point);
                label.setType(LABEL_PUBLIC_TYPE);
                label.setCreateUser(currUserId);
                label.setUpdateUser(currUserId);
                if (!labelService.save(label)) {
                    break;// 保存失败直接跳过
                }
                labelId = label.getId();
            }else {// 已存在则直接引用已存在标签ID
                labelId = first.get().getId();
            }
            // 插入题目/标签关系表
            ItemLabel itemLabel = new ItemLabel();
            itemLabel.setItemId(subjectItem.getId());
            itemLabel.setLabelId(labelId);
            itemLabel.setType(LABEL_PUBLIC_TYPE);
            itemLabel.setCreateUser(currUserId);
            itemLabel.setUpdateUser(currUserId);
            labelUpdate += itemLabelMapper.insert(itemLabel);
        }
        if (labelCount != labelDelete || labelUpdate != dto.getKnowledgePoints().size()){
            throw new DatabaseOperationException("标签更新失败");
        }
        return true;
    }

    /**
     * 删除主观题
     * @param ids 主观题ID集合
     * @param currUserId 当前登录用户ID
     * @return 删除结果，true为成功
     * @throws Exception 异常
     */
    @Override
    @Transactional
    public boolean removeSubjectItem(List<Long> ids, Long currUserId) throws Exception{
        if (CollectionUtils.isEmpty(ids)) {
            return false;
        }
        // 校验处理结果
        int removeItem = 0;
        int removeBook = 0;
        int removeLabel = 0;
        int removeImage = 0;

        for (Long id : ids) {
            SubjectItem subjectItem = this.getById(id);
            if (subjectItem == null) {
                throw new EntityInvalidException("题目不存在");
            }
            // 判断是否被试卷引用
            if (testPaperDetailMapper.selectCountByItemId(subjectItem.getId()) != 0) {
                throw new EntityInvalidException(String.format("题目【%s】被试卷引用，无法删除", subjectItem.getContent()));
            }
            // 删除题目实体
            subjectItem.setUpdateUser(currUserId);
            if (this.updateById(subjectItem) && this.removeSubjectById(id)) {
                removeItem++;
            }
            // 删除标签关系
            LambdaQueryWrapper<ItemLabel> labelLambdaQueryWrapper = new LambdaQueryWrapper<>();
            labelLambdaQueryWrapper.eq(ItemLabel::getItemId, id);
            Integer labelCount = itemLabelMapper.selectCount(labelLambdaQueryWrapper);
            if (itemLabelMapper.delete(labelLambdaQueryWrapper) == labelCount){
                removeLabel++;
            }
            // 删除图片关系
            LambdaQueryWrapper<ItemImage> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
            imageLambdaQueryWrapper.eq(ItemImage::getItemId, id);
            Integer imageCount = itemImageMapper.selectCount(imageLambdaQueryWrapper);
            if (itemImageMapper.delete(imageLambdaQueryWrapper) == imageCount) {
                removeImage++;
            }
            // 删除章节关系
            LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
            bookItemLambdaQueryWrapper.eq(BookItem::getItemId, id);
            Integer count = bookItemMapper.selectCount(bookItemLambdaQueryWrapper);
            if (bookItemMapper.delete(bookItemLambdaQueryWrapper) == count){
                removeBook++;
            }
            // 删除练习记录
            itemStudentService.remove(new LambdaQueryWrapper<ItemStudent>().eq(ItemStudent::getItemId, subjectItem.getId()));
            // 删除题目收藏
            collectService.remove(new LambdaQueryWrapper<Collect>().eq(Collect::getItemId, subjectItem.getId()));
            // 删除笔记
            noteService.remove(new LambdaQueryWrapper<Note>().eq(Note::getItemId, subjectItem.getId()));
        }
        if (removeItem != ids.size()) {
            throw new DatabaseOperationException("删除实体失败");
        }

        if (removeBook != ids.size()) {
            throw new DatabaseOperationException("删除章节关系失败");
        }

        if (removeLabel != ids.size()) {
            throw new DatabaseOperationException("删除标签关系失败");
        }

        if (removeImage != ids.size()) {
            throw new DatabaseOperationException("删除图片关系失败");
        }
        return true;
    }

    /**
     * 分页获取主观题列表
     * @param param 分页查询参数
     * @return 分页查询主观题列表结果
     */
    @Override
    public PageR<SubjectItemListResult> getSubjectItemListByPage(SubjectItemPageParam param){
        // 查询符合条件的题目
        Page<SubjectItem> page = new Page<>(param.getPage(), param.getPageSize());
        page.addOrder(OrderItem.desc("update_time"));
        LambdaQueryWrapper<SubjectItem> queryWrapper = buildSubjectItemListQueryWrapper(param);
        this.page(page, queryWrapper);

        // 构造结果集
        PageR<SubjectItemListResult> pageR = new PageR<>();
        BeanUtils.copyProperties(page, pageR, "records");
        List<SubjectItemListResult> results = transferSubjectItemToListResult(page.getRecords());
        pageR.setRecords(results);
        return pageR;
    }

    /**
     * 通过SubjectItemPageParam构造queryWrapper
     * @param param SubjectItemPageParam参数封装
     * @return queryWrapper
     */
    private LambdaQueryWrapper<SubjectItem> buildSubjectItemListQueryWrapper(SubjectItemPageParam param){
        LambdaQueryWrapper<SubjectItem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(param.getContent() != null, SubjectItem::getContent, param.getContent());// 题目模糊查询
        queryWrapper.eq(param.getDifficultyId() != null, SubjectItem::getDifficultyId, param.getDifficultyId());// 难易成都
        queryWrapper.eq(param.getCategory() != null, SubjectItem::getCategory, param.getCategory());// 病案类别
        // 如果有书籍ID
        if (param.getBookId() != null) {
            // 查询书籍章节关联的题目
            // 获取指定书籍节点及其子节点
            List<Book> books = bookService.getBookList();
            List<Long> bookIds = bookService.findChildNode(param.getBookId(), books).stream().map(Book::getId).collect(Collectors.toList());
            bookIds.add(param.getBookId());// 加入本
            LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
            bookItemLambdaQueryWrapper.in(BookItem::getBookId, bookIds);
            List<BookItem> bookItems = bookItemMapper.selectList(bookItemLambdaQueryWrapper);
            List<Long> itemIds = bookItems.stream().map(BookItem::getItemId).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(itemIds)) {
                queryWrapper.in(SubjectItem::getId, itemIds);
            }else {
                queryWrapper.apply("1 != 1");// 如果题目ID为空则查询为空
            }
        }
        // 如果有标签
        if (!CollectionUtils.isEmpty(param.getLabels())) {
            // 获取指定标签关联的题目ID
            List<Long> ids = new ArrayList<>();
            for (String label : param.getLabels()) {
                ids.addAll(itemLabelMapper.selectItemIdByLabelName(label));
            }
            if (!CollectionUtils.isEmpty(ids)) {
                queryWrapper.in(SubjectItem::getId, ids);
            }else {
                queryWrapper.apply("1 != 1");// 如题目ID为空则查询为空
            }
        }
        return queryWrapper;
    }

    /**
     * 批量导入主观题
     * @param file excel文件
     * @param currUserId 当前登录用户ID
     * @return 上传结果，true为成功
     * @throws IOException 异常
     */
    @Override
    @Transactional
    public BatchImportResult<SubjectItemExcel> importSubjectItem(MultipartFile file, Long currUserId) throws IOException {
        // 解析Excel文件，获取题目信息
        ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
        reader.addHeaderAlias("章节", "book");
        reader.addHeaderAlias("难易级别", "difficulty");
        reader.addHeaderAlias("病症类别", "category");
        reader.addHeaderAlias("知识点", "knowledgePoint");
        reader.addHeaderAlias("年龄", "age");
        reader.addHeaderAlias("性别", "gender");
        reader.addHeaderAlias("病症案例主诉", "content");
        reader.addHeaderAlias("诊断", "ansDiagnose");
        reader.addHeaderAlias("病机", "ansCause");
        reader.addHeaderAlias("治法", "ansMethod");
        reader.addHeaderAlias("处方", "ansPrescription");
        reader.addHeaderAlias("药物", "ansMedicine");
        reader.addHeaderAlias("病症解析", "analysis");
        reader.addHeaderAlias("诊断提示语", "tipDiagnose");
        reader.addHeaderAlias("病机提示语", "tipCause");
        reader.addHeaderAlias("治法提示语", "tipMethod");
        reader.addHeaderAlias("处方提示语", "tipPrescription");
        reader.addHeaderAlias("药物提示语", "tipMedicine");
        List<SubjectItemExcel> subjectItemExcels = reader.readAll(SubjectItemExcel.class);
        // 获取书籍信息
        List<Book> books = bookService.getBookList();
        // 获取难易级别信息
        LambdaQueryWrapper<Code> codeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        codeLambdaQueryWrapper.eq(Code::getType, AppProperties.CODE_TYPE_DIFFICULTY);
        List<Code> difficultyList = codeService.list(codeLambdaQueryWrapper);
        // 获取病症类别列表
        codeLambdaQueryWrapper.clear();
        codeLambdaQueryWrapper.eq(Code::getType, CODE_CATEGORY_TYPE);
        List<Code> categoryList = codeService.list(codeLambdaQueryWrapper);
        // 获取标签列表
        List<Label> labels = labelService.list();
        // 构造结果集
        BatchImportResult<SubjectItemExcel> result = new BatchImportResult<>();
        List<SubjectItemExcel> successList = new ArrayList<>();// 校验通过列表
        List<SubjectItemExcel> failList = new ArrayList<>();// 校验失败列表
        List<SubjectItemUpdateDto> dtoList = new ArrayList<>();// 构造Dto列表，用于确认后进行保存

        // 校验导入题目信息
        for (SubjectItemExcel excel : subjectItemExcels) {
            // 校验内容是否为空
            if (!StringUtils.hasText(excel.getContent())) {
                excel.setMsg("题目内容为空");
                failList.add(excel);
                continue;
            }
            // 校验药物内容
            if (!StringUtils.hasText(excel.getAnsMedicine())) {
                excel.setMsg("药物答案为空");
                failList.add(excel);
                continue;
            }
            // 校验所属章节
            Optional<Book> node = books.stream().filter(book -> book.getName().equals(excel.getBook()) && book.getType().equals(NODE_TYPE)).findFirst();
            if (!node.isPresent()) {
                excel.setMsg("章节不存在");
                failList.add(excel);
                continue;
            }
            // 校验难度等级
            Optional<Code> difficulty = difficultyList.stream().filter(diff -> diff.getName().equals(excel.getDifficulty())).findFirst();
            if (!difficulty.isPresent()) {
                excel.setMsg("难易级别不存在");
                failList.add(excel);
                continue;
            }
            // 校验病症类别
            Optional<Code> category = categoryList.stream().filter(code -> code.getName().equals(excel.getCategory())).findFirst();
            Code categoryCode = null;
            if (!category.isPresent()) {
                Code code = new Code();
                code.setName(excel.getCategory());
                code.setCode(PinyinUtil.getFirstLetter(excel.getCategory(), ""));
                code.setSequence(1);
                code.setType(AppProperties.CODE_TYPE_CATEGORY);
                code.setCreateUser(currUserId);
                code.setUpdateUser(currUserId);
                if (!codeService.save(code)) {
                    excel.setMsg("病症类别创建失败");
                    failList.add(excel);
                    continue;// 保存失败则跳过
                }
                categoryCode = code;
            }else {
                categoryCode = category.get();
            }

            // 检测标签存在，不存在则添加
            String[] points = excel.getKnowledgePoint().split("\\|");
            boolean flag = true;// 标志标签是否有创建失败的
            for (String point : points) {// 遍历标签判断是否已存在
                Optional<Label> first = labels.stream().filter(label -> label.getName().equals(point)).findFirst();
                if (!first.isPresent()){// 如果标签不存在则创建一个
                    Label label = new Label();
                    label.setName(point);
                    label.setType(LABEL_PUBLIC_TYPE);
                    label.setCreateUser(currUserId);
                    label.setUpdateUser(currUserId);
                    if (!labelService.save(label)) {
                        flag = false;
                        break;// 保存失败直接跳过
                    }
                }
            }
            if (!flag){
                excel.setMsg("标签创建失败");
                failList.add(excel);
                continue;
            }

            // 校验通过，加入成功集
            excel.setMsg("校验通过");
            successList.add(excel);
            // 构造Dto
            SubjectItemUpdateDto dto = new SubjectItemUpdateDto();
            BeanUtils.copyProperties(excel, dto);
            dto.setBookId(node.get().getId());
            dto.setDifficultyId(difficulty.get().getId());
            dto.setCategory(categoryCode.getId());
            dto.setKnowledgePoints(Arrays.asList(points));
            dto.setAge(Integer.parseInt(excel.getAge()));
            dto.setGender(excel.getGender().equals("男") ? 1 : 0);
            dtoList.add(dto);
        }
        // 构造结果封装对象
        result.setUuid(UUID.randomUUID().toString());
        result.setTotal(subjectItemExcels.size());
        result.setSuccessCount(successList.size());
        result.setSuccessList(successList);
        result.setFailCount(failList.size());
        result.setFailList(failList);
        // 存入缓存，等待前端确认，超过5分钟自动清除
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_SUBJECT_RES.getCacheKey(result.getUuid()), result, 5, TimeUnit.MINUTES);
        redisUtil.setCacheObject(CachePrefix.PC_IMPORT_SUBJECT_DTO.getCacheKey(result.getUuid()), dtoList, 5, TimeUnit.MINUTES);
        log.info("主观题上传ID：{} 存入缓存", result.getUuid());
        return result;
    }

    /**
     * 确认导入主观题
     * @param uuid 导入uuid
     * @param currUserId 当前登录用户ID
     * @return 导入结果，true为成功
     * @throws DatabaseOperationException 数据库操作异常
     */
    @Override
    @Transactional
    public boolean confirmBatchClozeItem(String uuid, Long currUserId) throws DatabaseOperationException{
        // 从缓存中取出数据
        List<SubjectItemUpdateDto> dtoList = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_SUBJECT_DTO.getCacheKey(uuid));
        if (dtoList == null) {
            return false;
        }
        int cnt = 0;// 保存计数器
        for (SubjectItemUpdateDto dto : dtoList) {
            if (this.addSubjectItem(dto, currUserId)) {
                cnt++;
            }
        }
        // 判断是否全部保存成功
        if (cnt != dtoList.size()) {
            throw new DatabaseOperationException("数据库保存失败");
        }
        // 成功则清除缓存
        redisUtil.deleteObject(CachePrefix.PC_IMPORT_SUBJECT_DTO.getCacheKey(uuid));
        return true;
    }

    /**
     * 通用填空题导出
     * @param excels 填空题导出封装对象列表
     * @param response http响应对象
     * @throws IOException 异常
     */
    public void exportSubjectItemCommon(List<SubjectItemExcel> excels, HttpServletResponse response) throws IOException {
        // 设置导出列名
        ExcelWriter writer = ExcelUtil.getWriter();
        writer.addHeaderAlias("book", "章节");
        writer.addHeaderAlias("difficulty", "难易级别");
        writer.addHeaderAlias("category", "病症类别");
        writer.addHeaderAlias("knowledgePoint", "知识点");
        writer.addHeaderAlias("age", "年龄");
        writer.addHeaderAlias("gender", "性别");
        writer.addHeaderAlias("content", "病症案例主诉");
        writer.addHeaderAlias("ansDiagnose", "诊断");
        writer.addHeaderAlias("ansCause", "病机");
        writer.addHeaderAlias("ansMethod", "治法");
        writer.addHeaderAlias("ansPrescription", "处方");
        writer.addHeaderAlias("ansMedicine", "药物");
        writer.addHeaderAlias("analysis", "病症解析");
        writer.addHeaderAlias("tipDiagnose", "诊断提示语");
        writer.addHeaderAlias("tipCause", "病机提示语");
        writer.addHeaderAlias("tipMethod", "治法提示语");
        writer.addHeaderAlias("tipPrescription", "处方提示语");
        writer.addHeaderAlias("tipMedicine", "药物提示语");
        writer.addHeaderAlias("msg", "导出/导出状态");

        // 响应数据
        writer.write(excels, true);

        // 导出数据
        ExcelExportUtil.commonExport(writer, response);
    }

    /**
     * 导出校验失败的主观题信息
     * @param uuid 导入uuid
     * @param response http响应对象
     * @throws Exception 异常
     */
    @Override
    public void exportImportFailedSubjectItem(String uuid, HttpServletResponse response) throws Exception {
        // 从缓存中获取数据
        BatchImportResult<SubjectItemExcel> result = redisUtil.getCacheObject(CachePrefix.PC_IMPORT_SUBJECT_RES.getCacheKey(uuid));
        if (result == null) {
            throw new Exception("缓存不存在或已过期");
        }
        exportSubjectItemCommon(result.getFailList(), response);
    }

    /**
     * 导出主观题
     * @param param 分页查询参数
     * @param response http响应对象
     * @throws IOException 异常
     */
    @Override
    public void exportSubjectItem(SubjectItemPageParam param, HttpServletResponse response) throws IOException {
        // 获取书籍信息
        List<Book> books = bookService.getBookList();
        // 获取难易级别信息
        LambdaQueryWrapper<Code> codeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        codeLambdaQueryWrapper.eq(Code::getType, CODE_DIFF_TYPE);
        List<Code> difficultyList = codeService.list(codeLambdaQueryWrapper);
        // 获取病症类别列表
        codeLambdaQueryWrapper.clear();
        codeLambdaQueryWrapper.eq(Code::getType, CODE_CATEGORY_TYPE);
        List<Code> categoryList = codeService.list(codeLambdaQueryWrapper);

        // 查询符合条件的题目
        LambdaQueryWrapper<SubjectItem> queryWrapper = buildSubjectItemListQueryWrapper(param);
        List<SubjectItem> subjectItems = this.list(queryWrapper);
        // 获取题目章节关系
        List<Long> ids = subjectItems.stream().map(SubjectItem::getId).collect(Collectors.toList());
        List<BookItem> bookItems = bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>()
                .eq(BookItem::getType, AppProperties.ITEM_TYPE_CLOZE)
                .in(!CollectionUtils.isEmpty(ids), BookItem::getItemId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));

        // 构造结果集
        List<SubjectItemExcel> excels = new ArrayList<>();
        for (SubjectItem subjectItem : subjectItems) {
            SubjectItemExcel excel = new SubjectItemExcel();
            BeanUtils.copyProperties(subjectItem, excel);
            // 查找章节节点名称
            books.stream().filter(book -> bookItems
                    .stream()
                    .filter(bookItem -> bookItem.getItemId().equals(subjectItem.getId()))
                    .map(BookItem::getBookId)
                    .collect(Collectors.toList())
                    .contains(book.getId()))
                    .findFirst()
                    .ifPresent(book -> {
                        excel.setBook(book.getName());
                    });
            // 查找难易级别名称
            difficultyList.stream().filter(code -> code.getId().equals(subjectItem.getDifficultyId())).findFirst().ifPresent(code -> {
                excel.setDifficulty(code.getName());
            });
            // 查找病症归类
            categoryList.stream().filter(code -> code.getId().equals(subjectItem.getCategory())).findFirst().ifPresent(code -> {
                excel.setCategory(code.getName());
            });
            // 年龄
            excel.setAge(subjectItem.getAge().toString());
            // 性别
            excel.setGender(subjectItem.getGender() == 1 ? "男" : "女");
            excel.setMsg("正常导出");
            excels.add(excel);
        }
        // 响应导出
        exportSubjectItemCommon(excels, response);
    }

    /**
     * 根据IDs获取主观题信息
     * @param ids 主观题ID列表
     * @return 主观题信息
     */
    @Override
    public List<SubjectItemListResult> getSubjectItemByIds(List<Long> ids){
        // 获取指定题目
        List<SubjectItem> subjectItems = this.listByIds(ids);
        // 转换结果
        return transferSubjectItemToListResult(subjectItems);
    }

    /**
     * 将主观题实体类转换为列表展示封装类
     * @param subjectItems 主观题实体类
     * @return 列表展示封装类集合
     */
    @Override
    public List<SubjectItemListResult> transferSubjectItemToListResult(List<SubjectItem> subjectItems){
        // 准备数据
        List<Long> ids = subjectItems.stream().map(SubjectItem::getId).collect(Collectors.toList());
        List<BookItem> bookItems = bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>()
                .in(!CollectionUtils.isEmpty(ids), BookItem::getItemId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));
        List<Book> books = bookService.getBookList();
        List<ItemImage> itemImages = itemImageMapper.selectList(new LambdaQueryWrapper<ItemImage>()
                .in(!CollectionUtils.isEmpty(ids), ItemImage::getItemId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));
        LambdaQueryWrapper<Code> codeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        codeLambdaQueryWrapper.eq(Code::getType, CODE_CATEGORY_TYPE);
        List<Code> codes = codeService.list(codeLambdaQueryWrapper);
        // 构造结果集
        return subjectItems.stream().map(subjectItem -> {
            SubjectItemListResult subjectItemListResult = new SubjectItemListResult();
            BeanUtils.copyProperties(subjectItem, subjectItemListResult);
            subjectItemListResult.setKnowledgePoints(Arrays.asList(subjectItem.getKnowledgePoint().split("\\|")));
            codes.stream().filter(code -> code.getId().equals(subjectItem.getCategory())).findFirst().ifPresent(code -> subjectItemListResult.setCategoryName(code.getName()));// 病案类别名
            // 查询所属章节
            subjectItemListResult.setBooks(bookService.getBooksOfItem(subjectItem.getId(), bookItems, books));
            // 查询配图
            subjectItemListResult.setImageIds(itemImages
                    .stream()
                    .filter(itemImage -> itemImage.getItemId().equals(subjectItem.getId()))
                    .map(ItemImage::getImageId)
                    .collect(Collectors.toList()));
            return subjectItemListResult;
        }).collect(Collectors.toList());
    }

    /**
     * 获取答案为列表
     * @param subjectItem 主观题实体
     * @return 答案列表
     */
    @Override
    public List<String> ansToStringList(SubjectItem subjectItem){
        return new ArrayList<String>(){{
            if (StringUtils.hasText(subjectItem.getAnsDiagnose())){
                add(subjectItem.getAnsDiagnose());
            }
            if (StringUtils.hasText(subjectItem.getAnsCause())){
                add(subjectItem.getAnsCause());
            }
            if (StringUtils.hasText(subjectItem.getAnsMethod())){
                add(subjectItem.getAnsMethod());
            }
            if (StringUtils.hasText(subjectItem.getAnsPrescription())){
                add(subjectItem.getAnsPrescription());
            }
            if (StringUtils.hasText(subjectItem.getAnsMedicine())){
                add(subjectItem.getAnsMedicine());
            }
        }};
    }

    /**
     * 根据ID获取主观题（缓存）
     * @param id 主观题ID
     * @return 主观题
     */
    @Override
    @Cacheable(value = AppProperties.ENTITY_CACHE_ITEM_SUBJECT, key = "#id", unless = "#result == null")// 缓存25小时
    public SubjectItem getSubjectItemById(Long id){
        return this.getById(id);
    }

    /**
     * 根据ID删除主观题（缓存）
     * @param id 主观题ID
     * @return 删除结果
     */
    @Override
    @CacheEvict(value = AppProperties.ENTITY_CACHE_ITEM_SUBJECT, key = "#id", condition = "#id != null")
    public boolean removeSubjectById(Long id){
        return this.removeById(id);
    }
}
